package dwarffortress.io;

import java.io.*;

public class DFInputStream extends InputStream {
    public static final int MAX_REASONABLE_ARRAY_LENGTH = 30000;
    public static final int MAX_REASONABLE_STRING_LENGTH = 300;
    private CountingInputStream stream;
    private OutputStream attached_output;


    public DFInputStream(InputStream stream) {
        this.stream = new CountingInputStream(stream);
    }

    public void readFully(byte[] b) throws IOException {
        int pos = 0;
        while (pos < b.length) {
            int count = read(b, pos, b.length - pos);
            if (count < 0) {
                throw new EOFException("End of file reached in Read Fully");
            }
            pos += count;
        }
    }

    public String readDFString() throws IOException {
        int len = readShort();
        checkStringLength(len);
        byte[] buf = new byte[len];
        readFully(buf);
        return new String(buf);
    }

    public void checkArrayLength(int len) {
        if (len < 0) {
            throw new StrangeSizeException(String.format("Negative size %d at offset %x", len, stream.getOffset() - 4));
        }
        if (len > MAX_REASONABLE_ARRAY_LENGTH) {
            throw new StrangeSizeException(String.format("Strange size %d at offset %x", len, stream.getOffset() - 4));
        }
    }

    private void checkStringLength(int len) {
        if (len < 0) {
            throw new StrangeSizeException(String.format("Negative string length %d at offset %x", len, stream.getOffset() - 2));
        }
        if (len > MAX_REASONABLE_STRING_LENGTH) {
            throw new StrangeSizeException(String.format("Strange string length %d at offset %x", len, stream.getOffset() - 2));
        }
    }

    public short readShort() throws IOException {
        byte[] buf = new byte[2];
        readFully(buf);
        return (short) makeLong(buf);
    }

    public int readInt() throws IOException {
        byte[] buf = new byte[4];
        readFully(buf);
        return (int) makeLong(buf);
    }

    public long readLong() throws IOException {
        byte[] buf = new byte[8];
        readFully(buf);
        return makeLong(buf);
    }

    public byte readByte() throws IOException {
        byte[] res = new byte[1];
        readFully(res);
        return res[0];
    }

    public int[] readIntList() throws IOException {
        int len = readInt();
        checkArrayLength(len);
        int[] result = new int[len];
        for (int i = 0; i < len; i++) {
            result[i] = readInt();
        }
        return result;
    }

    public byte[] readByteList() throws IOException {
        int len = readInt();
        checkArrayLength(len);
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++) {
            result[i] = readByte();
        }
        return result;
    }

    public short[] readShortList() throws IOException {
        int len = readInt();
        checkArrayLength(len);
        short[] result = new short[len];
        for (int i = 0; i < len; i++) {
            result[i] = readShort();
        }
        return result;
    }

    public String[] readStringList() throws IOException {
        int len = readInt();
        checkArrayLength(len);
        String[] result = new String[len];
        for (int i = 0; i < len; i++) {
            result[i] = readDFString();
        }
        return result;

    }


    private long makeLong(byte[] buf) {
        long result = 0;
        for (int i = buf.length - 1; i >= 0; i--) {
            result = result << 8;
            result = result | (((int) buf[i]) & 0xff);
        }
        return result;
    }


    public void attachOutput(OutputStream stream) {
        attached_output = stream;
    }

    public OutputStream detachOutput() {
        OutputStream result = attached_output;
        attached_output = null;
        return result;
    }

    public int read() throws IOException {
        int result = stream.read();
        if (result >= 0 && null != attached_output) {
            attached_output.write(result);
        }
        return result;
    }

    @Override
    public void close() throws IOException {
        stream.close();
        super.close();
    }

    public int getOffset() {
        return stream.getOffset();
    }
}
